﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Ams.Extensions;
using Ams.Mvc.Bootstrap.Annotations;

namespace Ams.Mvc.Bootstrap
{
    public static class BootstrapDropDownExtensions
    {
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
        {
            return DropDownFor(helper, expression, null/*selectList*/, null /* optionLabel */, null /* htmlAttributes */);
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
        {
            return DropDownFor(helper, expression, selectList, null /* optionLabel */, null /* htmlAttributes */);
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
        {
            return DropDownFor(helper, expression, selectList, null /* optionLabel */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, IDictionary<string, object> htmlAttributes)
        {
            return DropDownFor(helper, expression, selectList, null /* optionLabel */, htmlAttributes);
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel)
        {
            return DropDownFor(helper, expression, selectList, optionLabel, null /* htmlAttributes */);
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
        {
            return DropDownFor(helper, expression, selectList, optionLabel, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        }
        public static MvcHtmlString DropDownFor<TModel, TProperty>(this BootstrapHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
        {
            if (expression == null)
            {
                throw new ArgumentNullException("expression");
            }
            
            //Defining the items for our select list
            var selectListItems = (selectList ?? new List<SelectListItem>()).ToList();

            //Get the property name and value, this will help us in our descision
            var propertyInfo = expression.GetPropertyInfo();
            var propertyName = propertyInfo.Name;
            var value = expression.Compile().Invoke(helper.ViewData.Model) as string;

            //First we'll look for the drop down attribute
            var dropDownAttribute = expression.GetPropertyInfo().Attribute<DropDownAttribute>();
            if (dropDownAttribute != null && selectListItems.None())
            {

                //They have the option to pass in a method name, if not, we'll look for a method named {PropertyName}Options on the current controller
                var optionsMethodName = String.IsNullOrWhiteSpace(dropDownAttribute.OptionsMethodName) ? propertyName + "Options" : dropDownAttribute.OptionsMethodName;

                //Get the controller and method
                var controller = helper.ViewContext.Controller;
                var method = controller.GetType().GetMethods().FirstOrDefault(i => i.Name == optionsMethodName && i.GetParameters().None());

                //If not method was found then there's not much we can do about it
                if (method != null)
                {
                    //Attempt to get the options
                    var options = method.Invoke(controller, null);

                    //These are the only 2 types of list
                    var dict = options as Dictionary<string, string>;
                    var numDict = options as Dictionary<string, int>;
                    var list = options as IEnumerable<object>;

                    if (dict != null)
                    {
                        selectListItems = dict.Select(i => new SelectListItem { Text = i.Value, Value = i.Key, Selected = i.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase) }).ToList();
                    }
                    else if (numDict != null)
                    {
                        selectListItems = numDict.Select(i => new SelectListItem { Text = i.Value.ToString(), Value = i.Key, Selected = i.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase) }).ToList();
                    }
                    else if (list != null)
                    {
                        selectListItems = list.Select(i => new SelectListItem { Text = i.ToString(), Value = i.ToString(), Selected = i.ToString().Equals(value, StringComparison.InvariantCultureIgnoreCase) }).ToList();
                    }
                }
            }
            
            //If they set a range attribute and none of the other stuff worked, then they probably want a list of numbers
            var range = expression.GetPropertyInfo().Attribute<RangeAttribute>();
            if (range != null && selectListItems.None())
            {
                for (var i = Double.Parse(range.Minimum.ToString()); i <= Double.Parse(range.Maximum.ToString()); i++)
                {
                    selectListItems.Add(new SelectListItem { Text = i.ToString(), Value = i.ToString(), Selected = i.ToString().Equals(value, StringComparison.InvariantCultureIgnoreCase) });
                }
            }

            if (expression.GetPropertyInfo().PropertyType.IsEnum && selectListItems.None())
            {
                Func<string,string> getDisplayText = enumMemberName =>
                {
                    var description = propertyInfo.PropertyType.GetField(enumMemberName).Attribute<DescriptionAttribute>();

                    return description != null
                            ? description.Description
                            : enumMemberName.Titlize();
                };

                selectListItems = Enum.GetNames(propertyInfo.PropertyType).Select(i => new SelectListItem
                {
                    Text = getDisplayText(i),
                    Value = i,
                    Selected = i.Equals(value)
                }).ToList();
            }

            htmlAttributes = htmlAttributes ?? new Dictionary<string, object>();
            HtmlMod.AppendToAttributeList(ref htmlAttributes, "class", "form-control");
            var html = SelectExtensions.DropDownListFor(helper.Html, expression, selectListItems, optionLabel, htmlAttributes);

            if (helper.Form != null && helper.Form.FormType == Form.Horizontal)
            {
                var divWrapper = new TagBuilder("div");
                divWrapper.AddCssClass(helper.Form.EditorClasses);
                divWrapper.InnerHtml = html.ToString();
                html = divWrapper.ToMvcHtmlString();
            }

            return html;
        }
    }
}
